跳到主要内容

Spring 事务的核心概念~

这篇算是上面那篇 Spring 笔记的 AOP 部分补充

事务的 ACID 原则

1、原子性(atomicity): 一个事务必须被视为一个不可分割的最小工作单元,要么全失败,要么全成功 2、一致性(consistency):例如在执行第三、四条语句之间时系统崩溃,前面执行的语句也不会生效 3、隔离性(isolation):多个业务可能操作同一个资源,防止数据损坏 4、持久性(durability):事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中

这里只是为了快速回顾,具体细节看 MySQL 事务那篇笔记

为什么使用 AOP 管理事务?

学习 MySQL 时确实不怎么需要使用事务,那是因为执行那些语句时,MySQL默认开启了自动提交。但是到了实际应用中一般都是非自动提交的,需要手动提交

其中事务有两个分类:

  • 声明式事务:受 aop 管理的方式,本质就是对方法前后的拦截
  • 编程式事务:手动通过 Try-Catch 的方式(就是硬编码的方式)

例如如下代码(使用的 JDBC)连续的几个数据库操作,如果成功的话,则提交完成一个事务,如果某个发生异常,则回滚。

public class TestTransaction1 {
public static void main(String[] args) throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement1 = null;
PreparedStatement preparedStatement2 = null;
try{
connection = JdbcUtils.getConnection();
// 关闭自动提交,事务会自动开启
connection.setAutoCommit(false);
String sql01 = "update account set money = money-100 where name = 'A'";
String sql02 = "update account set money = money+100 where name = 'B'";

preparedStatement1 = connection.prepareStatement(sql01);
preparedStatement1.executeUpdate();
preparedStatement2 = connection.prepareStatement(sql02);
preparedStatement2.executeUpdate();

// 成功就提交事务
connection.commit();
}catch (SQLException throwables) {
// 失败了就回滚
connection.rollback();
throwables.printStackTrace();
}finally {
// 完成之后重新打开自动提交
connection.setAutoCommit(true);
JdbcUtils.release(connection,preparedStatement1,null);
JdbcUtils.release(null,preparedStatement2,null);
}
}
}

但是手写这样的代码效率太低,而且很多操作都是重复的,所以可以使用 Spring 的事务管理来方便这一操作

Spring 的事务依赖包

参考资料 官方文档 Spring transaction

相关依赖

<!-- Spring 的 Transaction 包 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>

Spring 事务抽象

统一一致的事务抽象是 Spring 框架的一大优势,无论是全局事务还是本地事务,JTA、JDBC、Hibernate 还是 JPA,Spring 都使用统一的编程模型,使得应用程序可以很容易地在全局事务与本地事务,或者不同的事务框架之间进行切换。下图是 Spring 事务抽象的核心类图:

  • PlatformTransactionManager: (平台)事务管理器
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus: 事务运行状态

所谓事务管理,其实就是 “按照给定的事务规则来执行提交或者回滚操作”。

PlatformTransactionManager 接口

Spring 并不直接管理事务,而是提供了多种事务管理器 ,他们将事务管理的职责委托给 Hibernate 或者 JTA 等持久化机制所提供的相关平台框架的事务来实现。

而 Spring 事务管理器的接口就是这个 PlatformTransactionManager ,通过这个接口,Spring 为各个平台如 JDBC、Hibernate 等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

它里面定义了一些事务管理的方法,一般的事务管理器(例如 MyBatis 的 DataSourceTransactionManager)要集成 Spring 就是实现这个接口里面的三个方法

  • getTransaction:获取事务
  • commit:提交事务
  • rollback:回滚事务

如下所示:

public interface PlatformTransactionManager extends TransactionManager {

TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;

void commit(TransactionStatus status) throws TransactionException;

void rollback(TransactionStatus status) throws TransactionException;
}

刚刚也说了 Spring 中 PlatformTransactionManager 根据不同持久层框架所对应的接口实现类,几个比较常见的如下图所示

比如我们在使用 JDBC 或者 iBatis(就是 Mybatis)进行数据持久化操作时,我们的 xml 配置通常如下:

<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>

TransactionDefinition 接口

事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 接口,这个 TransactionDefinition 接口是定义了事务的一些属性

那么什么是事务属性呢?

事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了 5个方面。

TransactionDefinition 接口中定义了 5个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等的常量。

  • getIsolationLevel:获取事务的隔离级别(主要就是解决并发产生的问题,脏读、不可重复读、虚读)
  • getPropagationBehavior:获得事务的传播行为(就是 A 方法调用 B 方法时,B 看 A 是否有事务的各种情况)
  • getTimeout:获取超时时间(如果没有则是 -1,以秒为单位)
  • isReadOnly:设置只读(一般设置查询时为只读)
  • getName:获取事务的名称

事务的隔离级别

TransactionDefinition 接口中定义了五个表示隔离级别的常量(具体这些级别的作用可以参考 MySQL 事务那篇笔记):

ISOLATION_DEFAULT                   使用底层数据存储的默认隔离级别
ISOLATION_READ_UNCOMMITTED 不进行处理
ISOLATION_READ_COMMITTED 解决 脏读的问题
ISOLATION_REPEATABLE_READ 解决 脏读和不可重复读
ISOLATION_SERIALIZABLE 解决 脏读、不可重复读、虚读 但是效率最低(效率除第一个逐级递减)

它就是一个枚举

// Mysql 默认采用的 REPEATABLE_READ 隔离级别
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}

使用例:

@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED)
public class DefaultFooService implements FooService {
  public void getFoo(Foo foo) {
    // do something
  }

  //方法上注解属性会覆盖类注解上的相同属性
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateFoo(Foo foo) {
    // do something
  }
}

事务中的几种事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

总之就是 A 方法调用 B 方法时,B 看 A 是否有事务的各种情况,在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:

支持当前事务的情况:

TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

不支持当前事务的情况:

TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。

它也是一个枚举类型

public enum Propagation {  
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}

可以通过下面这种方式设置

public void methodA(){
methodB();
//doSomething
}

@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}

使用例

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_required(){
User1 user1 = new User1();
user1.setName("张三");
user1Service.addRequired(user1);

User2 user2 = new User2();
user2.setName("李四");
user2Service.addRequired(user2);

throw new RuntimeException();
}

事务超时属性

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

事务只读属性

事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。

所谓事务性资源就是指那些被事务管理的资源,比如数据源、JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。

回滚规则

这些规则定义了哪些异常会导致事务回滚而哪些不会。

默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。

同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

注意:RuntimeException 运行时异常和 Exception 非运行时异常。

事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。

@Transactional 默认只捕获 RuntimeException,而自定义异常捕获后也不会回滚

注意:当 @Transactional 注解 作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常(RuntimeException),就会回滚,数据库里面的数据也会回滚。

但是如果在 @Transactional 注解中如果不配置 rollbackFor 属性(即默认的情况),那么事务只会在遇到 RuntimeException 的时候才会回滚,加上 rollbackFor = Exception.class,可以让事务在遇到非运行时异常时也回滚。

使用例:

@Override
@Transactional(rollbackFor = TestException.class)
public void transOuter() {
productMapper.updateOrderQuantityPessimistic(product_code1);
((ProductService) AopContext.currentProxy()).transInner();
}

@Transactional(rollbackFor = Exception.class)
public void transInner() {
productMapper.updateOrderQuantityPessimistic(product_code);
if (true) {
throw new RuntimeException();
}
}

这样,自己抛出的 TestException 错误也能回滚~

TransactionStatus 接口

TransactionStatus 接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息。

PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。返回的 TransactionStatus 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。

TransactionStatus 接口接口内容如下:

public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}

Reference ~

参考资料 Spring事务传播行为详解 参考资料 【技术干货】Spring事务原理一探 参考资料 透彻的掌握 Spring 中 @transactional 的使用 参考资料 可能是最漂亮的 Spring 事务管理详解